package com.liu.shop.service.impl;

import com.baomidou.dynamic.datasource.toolkit.DynamicDataSourceContextHolder;
import com.liu.shop.common.ThreadPool.ShopExecutor;
import com.liu.shop.common.constant.OrderConstatns;
import com.liu.shop.common.exception.BusinessException;
import com.liu.shop.common.vo.*;
import com.liu.shop.entity.OrderDetailEntity;
import com.liu.shop.entity.OrderEntity;
import com.liu.shop.feign.AddressService;
import com.liu.shop.feign.CarService;
import com.liu.shop.feign.StockService;
import com.liu.shop.interceptor.LoginInterceptor;
import com.liu.shop.interceptor.OrderDynamicDatasourceInterceptor;
import com.liu.shop.mapper.OrderDao;
import com.liu.shop.service.OrderService;
import com.liu.shop.utils.OrderIdUtils;
import lombok.extern.slf4j.Slf4j;
import org.springframework.amqp.rabbit.core.RabbitTemplate;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;

import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.List;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;


/**
 * 使用多线程进行创建订单，因为使用多线程会导致很多问题的出现：
 * 1.用户的id是在主线程的拦截器中获取的，所以在新的线程中会没有（使用数据共享解决）
 * 2.feign拦截器中要使用的请求对象是主线程的，在新的线程中也是没有的（在主线程中获取请求对象后加入到新线程中）
 * 3.因为进行了分库分表，数据库的编号是在拦截器中获取的，主线程中有，新的线程中没有（在要使用分库的新线程中，
 * 再调用动态数据库拦截器中的获取数据库编号的方法）
 */
@Slf4j
@Service
public class OrderServiceImpl implements OrderService {
    @Autowired
    private LoginInterceptor loginInterceptor;

    @Autowired
    private OrderIdUtils OrderIdUtils;

    @Autowired
    private AddressService addressService;

    @Autowired
    private CarService carService;

    @Autowired
    private OrderDynamicDatasourceInterceptor orderDynamicDatasourceInterceptor;

    @Autowired
    private OrderDao orderMapper;

    @Autowired
    private StockService stockService;

    @Autowired
    private RabbitTemplate rabbitTemplate;

    /**
     * 使用多线程后的业务流程：1.查询地址信息任务和购物车信息任务是互不干扰，所以可以启动两个线程异步执行
     * 2.库存扣减任务需要先获取购物车信息，所以库存扣减任务和获取购物车信息任务要有先后顺序
     * 3.只有地址任务和库存任务全部执行完后，才能封装数据进行数据存人到数据库中
     * 4.启动订单数据存入数据库任务
     * 5.启动订单详情存入数据库任务
     */
    @Transactional //开启分布式事务
    @Override
    public String create(OrderVo orderVo) throws ExecutionException, InterruptedException {

        // 获取主线程的请求对象。
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();

        // 1.启动一个任务进行地址的查询，并返回地址信息
        CompletableFuture<AddressVo> addressVoFuture = CompletableFuture.supplyAsync(() -> {
            // 讲主线程的请求对象设置到新线程的请求对象中
            RequestContextHolder.setRequestAttributes(requestAttributes);

            // feign调用地址模块查询用户地址信息
            return addressService.getAddressVoById(orderVo.getAid());
        }, ShopExecutor.getExecutor());

        // 2.启动一个任务查询用户的购物车信息
        CompletableFuture<UserCarVo> userCarVoFuture = CompletableFuture.supplyAsync(() -> {
            // 讲主线程的请求对象设置到新线程的请求对象中
            RequestContextHolder.setRequestAttributes(requestAttributes);

            // feign调用地址模块查询用购物车信息
            UserCarVo userCar = carService.getUserCar(orderVo.getSelectGid());
            return userCar;
        }, ShopExecutor.getExecutor());
        userCarVoFuture.get();// 阻塞线程，购物车信息查询完成后才会继续往下执行

        // 3.启动一个任务扣减库存,要在购物车任务结束后执行
        CompletableFuture<Boolean> stockFuture = userCarVoFuture.thenApplyAsync((userCarVo) -> {
            RequestContextHolder.setRequestAttributes(requestAttributes);
            //
            List<StockVo> stockVoList = new ArrayList();
            for (StoreCarVo storeCarVo : userCarVo.getStoreCarVoList()) {
                for (CarVo carVo : storeCarVo.getCarVoList()) {
                    StockVo stockVo = new StockVo();
                    stockVo.setGid(carVo.getGid());
                    stockVo.setCount(carVo.getCount());
                    stockVoList.add(stockVo);
                }
            }
            // 调用库存模块，进行库存的扣减，返回一个boolean
            return stockService.lockStock(stockVoList);
        }, ShopExecutor.getExecutor());

        // 拿到线程的返回值进行判断库存是否扣减成功
        Boolean aBoolean = stockFuture.get();
        if (!aBoolean) {
            throw new BusinessException("扣减库存失败", 3001);
        }

        // 4、任务组合，讲地址任务和扣减库存任务组合在一起，只有地址任务和库存任务结束后才会往下执行
        CompletableFuture<Void> future  = CompletableFuture.allOf(addressVoFuture, stockFuture);
        future.get(); // 只有地址任务和库存任务结束后才会往下执行


        // 5.获取登录用户
        UserVo userVo = loginInterceptor.get();

        // 6.创建订单ID(yyyMMdd+用户ID后四位+4位随机数+流水号(唯一))
        String orderId = OrderIdUtils.createOrderId(userVo.getId());

        // 7、获取表的索引
        Integer tabIndex = orderDynamicDatasourceInterceptor.get();

        // 8.阻塞线程，只有在地址任务和购物车任务结束后才会往下执行
        AddressVo addressVo = addressVoFuture.get();
        UserCarVo userCarVo = userCarVoFuture.get();

        // 9.获取到地址信息和购物车信息，库存扣减也解决后开始封装数据
        CompletableFuture<Void> addOrderFuture = CompletableFuture.runAsync(() -> {
            // 重新设置数据库的编号
            orderDynamicDatasourceInterceptor.setOrderDataSource(userVo);

            // 订单数据封装
            OrderEntity orderEntity = new OrderEntity();
            orderEntity.setId(orderId);
            orderEntity.setUid(Integer.parseInt(userVo.getId()));
            orderEntity.setAddress(addressVo.getAddress());
            orderEntity.setPhone(addressVo.getPhone());
            orderEntity.setName(addressVo.getName());
            orderEntity.setStatus(1); // 1
            orderEntity.setTotalPrice(userCarVo.getTotalPrice());
            orderEntity.setCreateTime(new SimpleDateFormat("yyyy-MM-dd").format(new Date()));
            orderEntity.setPayType(orderVo.getPayType());

            // 讲封装好的订单数据存入到数据库中
            int order = orderMapper.createOrder(orderEntity, tabIndex);
        }, ShopExecutor.getExecutor());

        // 10、插入订单详情
        CompletableFuture<Void> addOrderDetailFuture = CompletableFuture.runAsync(() -> {
            // 重新设置数据库的编号
            orderDynamicDatasourceInterceptor.setOrderDataSource(userVo);

            List<OrderDetailEntity> odList = new ArrayList<>();
            for (StoreCarVo storeCarVo : userCarVo.getStoreCarVoList()) {
                for (CarVo carVo : storeCarVo.getCarVoList()) {

                    // 把用户购买的数据封装到订单详情中
                    OrderDetailEntity odEntity = new OrderDetailEntity();
                    odEntity.setCount(carVo.getCount());
                    odEntity.setGdesc(carVo.getGoodsVo().getGdesc());
                    odEntity.setGid(carVo.getGid());
                    odEntity.setGprice(carVo.getGoodsVo().getGprice());
                    odEntity.setGname(carVo.getGoodsVo().getGname());
                    odEntity.setGurl(carVo.getGoodsVo().getGoodsPngList().get(0).getUrl());
                    odEntity.setOid(orderId);
                    odEntity.setSid(storeCarVo.getStoreVo().getId()); //
                    odEntity.setSubTotal(carVo.getTotalPrices());

                    odList.add(odEntity);
                }
            }
//         批量插入订单详情
            int odCount = orderMapper.addOrderDetail(odList, tabIndex);
        }, ShopExecutor.getExecutor());

        // 11. 订单任务和订单详情任务全部执行完后再返回订单ID
        CompletableFuture<Void> orderFuture = CompletableFuture.allOf(addOrderFuture, addOrderDetailFuture);
        orderFuture.get();// 阻塞，只有订单任务和订单详情任务完成后才会往下执行

        // 根据用户下单的购物车商品进行，清空购物车
//        carService.clearUserCar(userVo.getId(), orderVo.getSelectGid());

        // 订单创建后开倒计时，发送消息到延时队列，时间到了后根据订单状态进行修改
        rabbitTemplate.convertAndSend(OrderConstatns.ORDER_EXCHANGE, "order.create", orderId);

        return orderId;
    }

    @Override
    public OrderEntity getOrderById(String orderId) {
        // 获取表的编号
        Integer tabIndex = orderDynamicDatasourceInterceptor.get();

        // 如果数据源没有设置就再设置一下
        if(tabIndex==null){
            tabIndex=setOrderDataSource(orderId);
        }
        return orderMapper.getOrderById(orderId,tabIndex);
    }

    // 因为修改订单的是支付宝回调，所以不会在拦截器中设置数据源，也没有当前登录的用户
    @Override
    public void updateOrderStatus(String orderId, Integer status) {
        Integer tabIndex = setOrderDataSource(orderId);

        // 5.更新订单状态
        orderMapper.updateOrderStatus(orderId, tabIndex, status);
    }

    /**
     * 根据订单编号查询库存信息
     * @param orderId
     * @return
     */
    @Override
    public List<StockVo> getStockVoListOrderId(String orderId) {
        // 1.设置数据源
        Integer tabIndex = setOrderDataSource(orderId);

        // 2.查询订单详情
        return orderMapper.getStockVoListOrderId(orderId,tabIndex);
    }

    /**
     * 根据订单编号获取用户id，设置数据源
     * @param orderId
     * @return
     */
    public Integer setOrderDataSource(String orderId) {

        // 1.从订单ID中截取用户ID后四位
        Integer lastUserId = OrderIdUtils.getLastUserIdByOrderId(orderId);

        // 2.计算数据库编号
        Integer dbIndex = (lastUserId % OrderConstatns.ORDER_DB_SIZE) + 1;

        // 3.计算表的索引
        Integer tabIndex = (lastUserId / OrderConstatns.ORDER_DB_SIZE % OrderConstatns.ORDER_TABLE_SIZE) + 1;

        // 4.设置数据源
        DynamicDataSourceContextHolder.push(OrderConstatns.ORDER_DATASOURCE_PRE + dbIndex);
        return tabIndex;
    }
}
